package jeopardy.server.accounts;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import jeopardy.common.Utils;
import jeopardy.common.protocol.useraccount.UserAccountConsts;
import jeopardy.common.protocol.useraccount.UserAccountRequest;
import jeopardy.common.protocol.useraccount.UserAccountResponse;
import jeopardy.common.protocol.useraccount.UserAccountUtils;
import jeopardy.server.ServerConsts;
import jeopardy.server.db.H2ServerDb;

import org.apache.log4j.Logger;

public class UserAccountsManager implements Runnable{

	private static Logger log = Logger.getLogger(UserAccountsManager.class);
	
	private ServerSocket ss;
	private H2ServerDb dbStore;
	
	public UserAccountsManager(ServerSocket ss, H2ServerDb dbStore){
		this.ss = ss;
		this.dbStore = dbStore;
	}
	@Override
	public void run() {
		log.info("Starting User Accounts Manager on " + ss.getLocalPort());
		
		ThreadPoolExecutor tpe = new ThreadPoolExecutor(
				ServerConsts.UAM_CORETHREADS, ServerConsts.UAM_POOLSIZE, 
				ServerConsts.UAM_THREAD_KEEPALIVE_MILLI, TimeUnit.MILLISECONDS, 
				new LinkedBlockingQueue<Runnable>(ServerConsts.UAM_MAX_WAITING_JOBS));
		
		while(ss.isBound() && !ss.isClosed()){
			Socket req = null;
			try{
				req = ss.accept();
				tpe.execute(new UserAccountServicer(req));
			}catch(IOException ioe){
				log.info("Error accepting connection from client", ioe);
			}catch(RejectedExecutionException ree){
				log.info("Failed to accept more user account request", ree);
				try{
					req.close();
				}catch(Exception e ){
					log.error("task rejected, and socket was not closed.  Turn on debug logging for more info");
					log.debug("task rejected, and socket was not closed.", e);
				}; 
			}catch(Exception e){
				log.error("Unknown exception.  Turn on debug logging for more info");
				log.debug("Error accepting/tasking new account request", e);
			}
		}
	}
	
	private class UserAccountServicer implements Runnable{
		private Socket s;
		UserAccountServicer(Socket s){
			this.s = s;
		}
		@Override
		public void run(){
			try{
				s.setSoTimeout(ServerConsts.UAM_CONN_KEEPALIVE_MILLI);
				byte[] readin = new byte[UserAccountConsts.USERACCOUNT_MAX_REQ_LEN];
				int numRead = Utils.readBytes(s.getInputStream(), readin);
				
				UserAccountRequest uar = UserAccountUtils.parseUserAccountRequest(Arrays.copyOf(readin, numRead));
				
				if(uar.isAuthRequest()){
					if(dbStore.authUser(uar.getUser(), uar.getPassword())){
						s.getOutputStream().write(UserAccountResponse.SUCCESS);
					}else{
						s.getOutputStream().write(UserAccountResponse.ERROR);
					}
				}else if(uar.isCreateRequest()){
					if(!UserAccountUtils.validateUserString(uar.getUser())){
						s.getOutputStream().write(UserAccountResponse.ERROR);
					}else if(!UserAccountUtils.validatePasswordString(uar.getPassword())){
						s.getOutputStream().write(UserAccountResponse.ERROR);
					}else{
						boolean success = dbStore.createUser(uar.getUser(), uar.getPassword());
						if(success){
							s.getOutputStream().write(UserAccountResponse.SUCCESS);
						}else{
							s.getOutputStream().write(UserAccountResponse.ERROR);
						}
					}
				}else{
					log.info("Invalid user account request, dropping.");
				}
				s.getOutputStream().flush();
				s.close();
			}catch(Exception e){
				log.info("Error servicing user account request");
				log.debug("Error servicing user account request", e);
				
			}finally{
				try{
					if(s != null && !s.isClosed()){
						s.close();
					}
				}catch(Exception ee){
					log.error("failed to close socket");
					log.debug("failed to close socket", ee);
				}
			}
		}
	}

}
